package cn.skyquant.quant4j.jforex.strategy;


import cn.skyquant.quant4j.api.dto.ResultDTO;
import cn.skyquant.quant4j.api.enums.BorkerType;
import cn.skyquant.quant4j.api.forex.ForexConfigSeriesDTO;
import cn.skyquant.quant4j.api.forex.ForexConfigSymbolDTO;
import cn.skyquant.quant4j.api.forex.ForexService;
import cn.skyquant.quant4j.api.forex.ForexServiceProxy;
import cn.skyquant.quant4j.jforex.sdk.enums.LabelType;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseOrderComment;
import cn.skyquant.quant4j.jforex.sdk.strategy.BaseStrategy;
import cn.skyquant.quant4j.jforex.sdk.strategy.InstrumentEntity;
import cn.skyquant.quant4j.jforex.sdk.strategy.OrderEx;
import cn.skyquant.quant4j.sdk.enums.TradeDirection;
import com.dukascopy.api.*;

import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 雅典娜测试版
 * 多货币策略
 *
 * @author harley-dog
 */
@Library("quant4j-api.jar;quant4j-sdk.jar;quant4j-jforex-sdk.jar;freemarker-2.3.28.jar;httpclient-4.5.6.jar;httpcore-4.4.10.jar;fastjson-1.2.49.jar;commons-codec-1.11.jar")
public class AthenaTest extends BaseStrategy {
    @Configurable("货币组id")
    public int symbolId = 42;
    @Configurable("加仓数列id")
    public int seriedId = 100;
    @Configurable("止损值")
    public int stoploss = -2600;
    @Configurable("开仓倍率")
    public int add = 1;
    @Configurable("强制开仓倍率")
    public double f_add = 2;

    ForexService forexService;


    @Override
    protected String getVersion() {
        return "001";
    }

    @Override
    protected String getName() {
        return "AthenaTest";
    }

    @Override
    protected String getEaName() {
        return "AthenaTest";
    }

    @Override
    protected void onTickEx(Instrument instrument, ITick tick) {
        process();
    }

    @Override
    protected void onBarEx(Instrument instrument, Period period, IBar askBar, IBar bidBar) {

    }

    @Override
    protected void onMessageEx(IMessage message) {
        if(message.getType()== IMessage.Type.ORDER_FILL_OK){
            IOrder order = message.getOrder();
            //out("fill:%s %s %s %s",order.getInstrument().name(),order.getOrderCommand().name(),order.getOpenPrice(),order.getAmount());
        }
        if(message.getType()==IMessage.Type.ORDER_CLOSE_OK){
            IOrder order = message.getOrder();
            //out("close:%s %s %s %s %s",order.getInstrument().name(),order.getOrderCommand().name(),order.getOpenPrice(),order.getAmount(),message.getReasons());
        }
    }

    @Override
    protected void onAccountEx(IAccount account) {

    }

    private void process() {
        List<IOrder> orderList = null;
        try {
            orderList = engine.getOrders();
        } catch (JFException e) {
            err("process.getOrders error:", e.getMessage());
            return;
        }
        //第一步，如果没有仓位，则开首仓
        if (current_index == -1) {
            if((context.getTime()-last_close_time)>sleep_time){
                //执行开首仓逻辑
                openOrder(0,0,orderList);
                last_close_time = 0;
                sleep_time = 0;
            }
            return;
        }
        long time = context.getTime();
        //余额
        double equity = account.getEquity();
        double loss = equity - first_equity;
        //到达总止损点，则止损
        if (loss < stoploss * add) {
            double total_amount = 0;
            for(IOrder order:orderList){
                total_amount+=order.getAmount();
            }
            super.addStatistics(first_time,last_time,first_equity,last_equity,current_index,loss,total_amount,(int)Math.round(Math.abs(last_equity-first_equity)));
            closeOrders(orderList);
            out("loss:%.2f < %d * %d, sl close",loss,stoploss,add);
            reset();
            last_close_time = context.getTime();
            sleep_time = Period.DAILY.getInterval() * super.sl_sleep_time;
            return;
        }
        ForexConfigSeriesDTO series = seriesList.get(current_index);
        //到达止盈点，则止盈
        if (loss > series.tp * add) {
            double total_amount = 0;
            for(IOrder order:orderList){
                total_amount+=order.getAmount();
            }
            super.addStatistics(first_time,last_time,first_equity,last_equity,current_index,loss,total_amount,(int)Math.round(Math.abs(last_equity-first_equity)));
            closeOrders(orderList);
            out("loss:%.2f > %d * %d, tp close",loss,series.tp,add);
            reset();
            last_close_time = context.getTime();
            sleep_time = Period.ONE_MIN.getInterval() * super.f_sleep_time;
            return;
        }
        //如果加到末仓，则等待
        if (current_index >= seriesList.size() - 1) {
            return;
        }
        //第三步，如果即不是末仓，又不是首仓，则判断时间
        ForexConfigSeriesDTO series_next = seriesList.get(current_index + 1);

        if (loss < (series_next.sl + (series_next.sl - series.sl) * f_add) * add) {
            out("loss=%.2f < %.2f, fadd",loss,(series_next.sl + (series_next.sl - series.sl) * f_add) * add);
            openOrder(current_index + 1,loss,orderList);
            return;
        }
        //时间不到等待
        if (time - last_time < time_r * series_next.time) {
            return;
        }
        if (loss < series_next.sl * add) {
            out("loss=%.2f < %d ,add",loss,series_next.sl * add);
            openOrder(current_index + 1,loss,orderList);
            return;
        }

    }

    private void reset() {
        current_index = -1;//当前仓
        first_equity = 0;//首仓余额
        first_time = 0;//首仓时间
        last_equity = 0;//最末仓的余额
        last_loss = 0;//最末仓的损失
        time_r = 1;//时间翻倍
        last_time = context.getTime();
    }

    //开首仓
    private void openOrder(int index,double loss,List<IOrder> orderList) {
        ForexConfigSeriesDTO seriesDTO = seriesList.get(index);
        //首先计算仓位
        //最小开仓单位*10 * 1个点 = 1USD
        //1个点 = 0.1/最小开仓单位
        //仓量 * 距离 = 钱
        //仓量 = 钱/距离
        //仓量 = (要盈利的钱-已损失的钱)/距离
        //1 = (10-10)/20
        for (ForexConfigSymbolDTO symbolDTO : symbolList) {
            Instrument instrument = Instrument.valueOf(symbolDTO.symbol);
            double exist_amount = 0;
            for(IOrder order:orderList){
                if(order.getInstrument() == instrument){
                    exist_amount += order.getAmount();
                }
            }
            InstrumentEntity ie = insMap.get(instrument);
            double exp_amount = (seriesDTO.tp.doubleValue()*add - loss)*10*ie.minAmount/seriesDTO.backpip.doubleValue();
            TradeDirection td = symbolDTO.tradeDirection;
            IEngine.OrderCommand orderCommand = td == TradeDirection.LONG ? IEngine.OrderCommand.BUY : IEngine.OrderCommand.SELL;
            OrderEx<AthenaTestComment> orderEx = new OrderEx(getName(), LabelType.M, seriedId, index, instrument, td);
            String label = orderEx.toString();
            double open_amount = exp_amount - exist_amount ;
            double real_open_amount ;
            if(open_amount<ie.minAmount){
                real_open_amount = ie.minAmount;
            }else {
                real_open_amount = open_amount;
            }
            out("[%d] open [%d].%s,exp_amount:[%s]=((tp[%d]*[%d]-loss[%.2f])/backpip[%d])*10*min_amount[%s],exist_amount=%s,open_amount=%s",
                    seriedId, index,symbolDTO.symbol,
                    ie.formatAmout(exp_amount),seriesDTO.tp,add,loss,seriesDTO.backpip,ie.minAmount,
                    ie.formatAmout(exist_amount),
                    ie.formatAmout(real_open_amount));
            try {
                this.engine.submitOrder(label, instrument, orderCommand, real_open_amount);
            } catch (JFException e) {
                err("openFirst Order error %s", e.getMessage());
            }
        }
        if (index == 0) {
            first_time = context.getTime();
            first_equity = account.getEquity();
            time_r = 1;
        }
        current_index = index;
        last_equity = account.getEquity();
        last_time = context.getTime();
        last_loss = account.getEquity() - first_equity;
    }

    private static final BigDecimal MILLION = new BigDecimal(1000000);
    private long last_close_time = 0;
    private long sleep_time = 0;

    private int current_index = -1;//当前仓
    private double first_equity = 0;//首仓余额
    private long first_time = 0;//首仓时间
    private double last_equity = 0;//最末仓的余额
    private double last_loss = 0;//最末仓的损失
    private int time_r = 1;//时间翻倍
    private long last_time;
    final Map<Instrument, InstrumentEntity> insMap = new HashMap<>();//交易品种实体
    private int total_stand_position = 0;

    List<ForexConfigSeriesDTO> seriesList;
    List<ForexConfigSymbolDTO> symbolList;

    @Override
    protected void init(InitEntity initEntity) {
        if (!isTestMode()) {
            initEntity.addReason("AthenaTest only run at test mode");
            return;
        }
        forexService = new ForexServiceProxy(account.getAccountId(), BorkerType.DUKASCOPY);
        ResultDTO<List<ForexConfigSeriesDTO>> seriesResult = forexService.findConfigSeries(seriedId);
        if (!seriesResult.okData() || seriesResult.data.isEmpty()) {
            initEntity.addReason("series is empty");
            return;
        }
        seriesList = seriesResult.data;
        Iterator<ForexConfigSeriesDTO> iterator = seriesList.iterator();
        while (iterator.hasNext()){
            ForexConfigSeriesDTO seriesDTO = iterator.next();
            if(seriesDTO.backpip==null || seriesDTO.backpip.intValue()==0
            || seriesDTO.tp==null || seriesDTO.tp.intValue()==0){
                iterator.remove();
            }
        }
        ResultDTO<List<ForexConfigSymbolDTO>> symbolResult = forexService.findConfigSymbol(symbolId);
        if (!symbolResult.okData() || symbolResult.data.isEmpty()) {
            initEntity.addReason("series is empty");
            return;
        }
        symbolList = symbolResult.data;

        symbolList.forEach(x -> {
            Instrument instrument = Instrument.valueOf(x.symbol);
            initEntity.b.add(instrument);
            insMap.put(instrument, new InstrumentEntity(instrument));
            total_stand_position += x.standardPosition;
        });
        initEntity.r = true;
    }


    private static class AthenaTestComment extends BaseOrderComment {

    }

}